use qjs_sys as q;
use crate::runtime::{JSRuntime};
use crate::value::{RawJsValue,AutoDropJSValue};
use std::ffi::{CString,CStr};
use std::collections::HashMap;

static mut EXPORT_PROR_DIC:Option<HashMap<usize,(usize,*mut q::JSCFunctionListEntry)>> = None;

pub struct JSContext {
   c_context:*mut q::JSContext,
   c_rt:*mut q::JSRuntime,
   proplist:Vec<Vec<q::JSCFunctionListEntry>>
}

impl JSContext {
  pub fn new(rt:&JSRuntime) -> Result<Self,String> {
      let c_ctx = unsafe{ q::JS_NewContext(rt.c_rt()) };
      if c_ctx.is_null() {
        Err(String::from("create JSContext fail"))
      }else {
        unsafe { EXPORT_PROR_DIC = Some(HashMap::default()) };
        Ok(JSContext {c_rt:rt.c_rt() ,c_context:c_ctx,proplist:Vec::default()}) 
      }
  }

  pub fn set_self_opaque(&mut self) {
    unsafe { q::JS_SetContextOpaque(self.c_ctx(),std::mem::transmute(self)); };
  }

  pub fn c_ctx(&self) -> *mut q::JSContext {
    self.c_context
  }

  pub fn c_rt(&self) -> *mut q::JSRuntime {
    self.c_rt
  }

  pub fn add_all_intrinsic(&self) {
    self.add_intrinsic_base_objects();
    self.add_intrinsic_date();
    self.add_intrinsic_eval();
    self.add_intrinsic_json();
    self.add_intrinsic_map_set();
    self.add_intrinsic_promise();
    self.add_intrinsic_proxy();
    self.add_intrinsic_reg_exp();
    self.add_intrinsic_string_normalize();
    self.add_intrinsic_typed_arrays();
    self.add_intrinsic_reg_exp_compiler();
  }

  /*
  pub fn set_max_stack_size(&self,size:usize) {
    unsafe{ q::JS_SetMaxStackSize(self.c_context,size) }
  }*/


  pub fn add_intrinsic_base_objects(&self) {
    unsafe{ q::JS_AddIntrinsicBaseObjects(self.c_context) }
  }

  pub fn add_intrinsic_date(&self) {
    unsafe{ q::JS_AddIntrinsicDate(self.c_context) }
  }

  pub fn add_intrinsic_eval(&self) {
    unsafe{ q::JS_AddIntrinsicEval(self.c_context) }
  }
  pub fn add_intrinsic_string_normalize(&self) {
    unsafe{ q::JS_AddIntrinsicStringNormalize(self.c_context) }
  }
  pub fn add_intrinsic_reg_exp_compiler(&self) {
    unsafe{ q::JS_AddIntrinsicRegExpCompiler(self.c_context) }
  }
  pub fn add_intrinsic_reg_exp(&self) {
    unsafe{ q::JS_AddIntrinsicRegExp(self.c_context) }
  }
  pub fn add_intrinsic_json(&self) {
    unsafe{ q::JS_AddIntrinsicJSON(self.c_context) }
  }
  pub fn add_intrinsic_proxy(&self) {
    unsafe{ q::JS_AddIntrinsicProxy(self.c_context) }
  }
  pub fn add_intrinsic_map_set(&self) {
    unsafe{ q::JS_AddIntrinsicMapSet(self.c_context) }
  }
  pub fn add_intrinsic_typed_arrays(&self) {
    unsafe{ q::JS_AddIntrinsicTypedArrays(self.c_context) }
  }
  pub fn add_intrinsic_promise(&self) {
    unsafe{ q::JS_AddIntrinsicPromise(self.c_context) }
  }

  pub fn eval(&self,code:&str,filename:&str,eval_type:i32) -> AutoDropJSValue {
    let mut str_code = String::from(code);
    str_code.insert_str(0, crate::INLINE_CODE);
   
    let c_code = CString::new(str_code.as_str()).unwrap();
    
    let c_filename = CString::new(filename).unwrap();
    let value_raw = unsafe {
            q::JS_Eval(
                self.c_context,
                c_code.as_ptr(),
                str_code.len(),
                c_filename.as_ptr(),
                eval_type,
            )
    };
    let ret_val = AutoDropJSValue::new(value_raw,self.c_context);
    
    if ret_val.inner().is_exception() {
      let err = self.get_exception();
      eprintln!("[JSERROR] {}",err.unwrap());
    }
    ret_val
  }

  pub fn eval_file(&self,path:&str) -> AutoDropJSValue {
    let file_string:String = std::fs::read_to_string(&path).unwrap();
    self.eval(file_string.as_str(),path,(q::JS_EVAL_TYPE_GLOBAL | q::JS_EVAL_TYPE_MODULE) as i32)
  }

  pub fn get_exception(&self) -> Result<String,String> {
     let raw = unsafe { q::JS_GetException(self.c_context) };
     let cjs_val = AutoDropJSValue::new(raw,self.c_context);
     let msg = cjs_val.inner().get_property_str("message",self.c_context).to_value(self.c_context)?;
     let stack = cjs_val.inner().get_property_str("stack",self.c_context).to_value(self.c_context)?;
     let mut ret_str = msg.to_string();
     ret_str.push_str(&stack.to_string());
     Ok(ret_str)
  }

  pub fn get_exception_raw(ctx:*mut q::JSContext) -> Result<String,String> {
      let raw = unsafe { q::JS_GetException(ctx) };
     let cjs_val = AutoDropJSValue::new(raw,ctx);
     let msg = cjs_val.inner().get_property_str("message",ctx).to_value(ctx)?;
     let stack = cjs_val.inner().get_property_str("stack",ctx).to_value(ctx)?;
     let mut ret_str = msg.to_string();
     ret_str.push_str(&stack.to_string());
     Ok(ret_str)
  }

  pub fn get_global_object(&self) -> AutoDropJSValue {
     let raw = unsafe {q::JS_GetGlobalObject(self.c_ctx()) };
     AutoDropJSValue::new(raw, self.c_ctx())
  }

  pub fn new_cfunction(&self,funcname:&str,len:i32,func:q::JSCFunction) -> RawJsValue {
    let c_funcname = CString::new(funcname).unwrap();
    let val = unsafe { q::JS_NewCFunction2(self.c_ctx(),func,c_funcname.as_ptr(),len,q::JSCFunctionEnum_JS_CFUNC_generic,0)  };
    RawJsValue(val)
  }

  pub fn define_property_value_str(&self,name:&str,this_obj:&RawJsValue,def_val:&RawJsValue,flag:u32) -> i32 {
   let c_name = CString::new(name).unwrap();
    unsafe {
     q::JS_DefinePropertyValueStr(self.c_ctx(),this_obj.0,c_name.as_ptr(),def_val.0,flag as i32)
    }
  }
 
  pub fn set_property_function_list(&mut self,obj:q::JSValue,proplist:&Vec<JSPropertyItem>) {
      let prop_ptr = property_list_to_c(self,proplist) ;
      unsafe {q::JS_SetPropertyFunctionList(self.c_ctx(),obj,prop_ptr,proplist.len() as i32)}
  }

  pub fn new_c_module(&self,module_name:&str,init_func:q::JSModuleInitFunc) -> *mut q::JSModuleDef {
    let c_module_name = CString::new(module_name).unwrap();
    unsafe {q::JS_NewCModule(self.c_context,c_module_name.as_ptr(),init_func) }
  }

  pub fn add_module_export_list(&mut self,m: *mut q::JSModuleDef,proplist:*mut q::JSCFunctionListEntry,len:i32) {
    unsafe { q::JS_AddModuleExportList(self.c_context,m,proplist,len); }
  }

  pub fn add_prop_list(&mut self,props:&Vec<JSPropertyItem>) -> *mut q::JSCFunctionListEntry {
    property_list_to_c(self, props)
  }

  pub fn set_module_export_list(&mut self,m: *mut q::JSModuleDef,proplist:*mut q::JSCFunctionListEntry,len:i32) {
    unsafe { q::JS_SetModuleExportList(self.c_context,m,proplist,len); }
  }

  pub fn add_c_module(&mut self,module_name:&str,prop_list:&Vec<JSPropertyItem>) {
    let prop_ptr = self.add_prop_list(prop_list);
   
    let c_module_name = CString::new(module_name).unwrap();
    let m_ptr = unsafe {q::JS_NewCModule(self.c_context,c_module_name.as_ptr(),Some(on_module_init)) };
    unsafe {
      let ref_map = &mut EXPORT_PROR_DIC;
     
      match ref_map {
        Some(m) => {
          m.insert(m_ptr as usize,(prop_list.len(),prop_ptr));
        },
        _ => ()
      };
    };
    unsafe { q::JS_AddModuleExportList(self.c_context,m_ptr,prop_ptr,prop_list.len() as i32);  }
  }
}


pub extern "C" fn on_module_init(ctx: *mut q::JSContext, m: *mut q::JSModuleDef) -> ::std::os::raw::c_int {
  let ref_map = unsafe { &EXPORT_PROR_DIC };
  let ptr_key = m as usize;
  let prop_ptr = match ref_map {
    Some(map) => map.get(&ptr_key).unwrap(),
    None => &(0,0 as *mut q::JSCFunctionListEntry)
  };
  
  let ret = unsafe { q::JS_SetModuleExportList(ctx,m,prop_ptr.1,prop_ptr.0 as i32) };
  ret
}


impl Drop for JSContext {
  fn drop(&mut self) {
    println!("drop JSContext");
    unsafe {q::JS_FreeContext(self.c_context);};
  }
}

pub enum EvalType {
  Global = 0,
  Module = 1
}

pub struct JSPropertyItem {
  name:*const i8,
  prop_flags: u8,
  def_type: u8,
  magic: i16,
  u: JSPropertyItemValue
}

impl JSPropertyItem {
  pub fn int32(name:&'static CStr,n:i32,flags:u8) -> JSPropertyItem {
    JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_PROP_INT32 as u8,
                    magic:0,
                    u:JSPropertyItemValue::Int32(n)
                   }
  }

  pub fn int64(name:&'static CStr,n:i64,flags:u8) -> JSPropertyItem {
     JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_PROP_INT64 as u8,
                    magic:0,
                    u:JSPropertyItemValue::Int64(n)
                   }
  }

  pub fn double(name:&'static CStr,n:f64,flags:u8) -> JSPropertyItem {
     JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_PROP_DOUBLE as u8,
                    magic:0,
                    u:JSPropertyItemValue::Double(n)
                   }
  }

  pub fn undefined(name:&'static CStr,flags:u8) -> JSPropertyItem {
     JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_PROP_UNDEFINED as u8,
                    magic:0,
                    u:JSPropertyItemValue::Undefined()
                   }
  }

  pub fn string(name:&'static CStr,str:&'static CStr,flags:u8) -> JSPropertyItem {
    JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_PROP_STRING as u8,
                    magic:0,
                    u:JSPropertyItemValue::String(str)
                   }
  }

  pub fn func(name:&'static CStr,func:q::JSCFunction,len:i32) -> JSPropertyItem {
    JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:(q::JS_PROP_WRITABLE | q::JS_PROP_CONFIGURABLE) as u8,
                    def_type:q::JS_DEF_CFUNC as u8,
                    magic:0,
                    u:JSPropertyItemValue::CFunc(func,len)
                   }
  }

  /*
  pub fn rfunc(name:&'static CStr,_func:q::JSCFunction,len:i32) -> JSPropertyItem {
    JSPropertyItem {
      name:name.as_ptr(),
      prop_flags:(q::JS_PROP_WRITABLE | q::JS_PROP_CONFIGURABLE) as u8,
      def_type:q::JS_DEF_CFUNC as u8,
      magic:0,
      u:JSPropertyItemValue::CFunc(Some(wrapper_func),len)
     }
  }
  */

  pub fn object(name:&'static CStr,prop_list:Vec<JSPropertyItem>,flags:u8) -> JSPropertyItem {
     JSPropertyItem {
                    name:name.as_ptr(),
                    prop_flags:flags,
                    def_type:q::JS_DEF_OBJECT as u8,
                    magic:0,
                    u:JSPropertyItemValue::Object(prop_list)
                   }
  }
}

/*
pub unsafe extern "C" fn wrapper_func(_ctx: *mut q::JSContext,_this_val: q::JSValue,_argc:std::os::raw::c_int,_argv: *mut q::JSValue) -> q::JSValue {
  RawJsValue::val_null()
}*/


pub enum JSPropertyItemValue {
  CFunc(q::JSCFunction,i32),
  CGetSet(q::JSCFunction,q::JSCFunction),
  CGetSetMagic(q::JSCFunction,q::JSCFunction,i32),
  String(&'static CStr),
  Int32(i32),
  Int64(i64),
  Double(f64),
  Undefined(),
  Object(Vec<JSPropertyItem>),
  Alias(String)
}

impl JSPropertyItemValue {
  pub fn to_c(&self,ctx:&mut JSContext) -> q::JSCFunctionListEntry__bindgen_ty_1 {
   match self {
      JSPropertyItemValue::Int32(num)  => q::JSCFunctionListEntry__bindgen_ty_1 { i32 : *num },
      JSPropertyItemValue::Int64(num)  => q::JSCFunctionListEntry__bindgen_ty_1 { i64 : *num },
      JSPropertyItemValue::Double(num) => q::JSCFunctionListEntry__bindgen_ty_1 { f64 : *num },
      JSPropertyItemValue::Undefined() => q::JSCFunctionListEntry__bindgen_ty_1 {i32 : 0},
      JSPropertyItemValue::String(c_str) => {
        q::JSCFunctionListEntry__bindgen_ty_1 {str : c_str.as_ptr()}
      },
      JSPropertyItemValue::CFunc(func,len) => {
         q::JSCFunctionListEntry__bindgen_ty_1 {func : 
           q::JSCFunctionListEntry__bindgen_ty_1__bindgen_ty_1 {
             length:*len as u8,
             cproto: (q::JSCFunctionEnum_JS_CFUNC_generic as u8),
             cfunc:q::JSCFunctionType { generic : *func }
           }
         }
      },
      JSPropertyItemValue::Object(lst) => {
        let inner_prop_list = property_list_to_c(ctx,lst);
        q::JSCFunctionListEntry__bindgen_ty_1 {
          prop_list : q::JSCFunctionListEntry__bindgen_ty_1__bindgen_ty_4 {
            tab:inner_prop_list,
            len:lst.len() as i32
          }
        }
      }
      _ => q::JSCFunctionListEntry__bindgen_ty_1 {i32 :0 },
    }
  }
}



pub fn property_list_to_c(ctx:&mut JSContext,lst:&Vec<JSPropertyItem>) -> *mut q::JSCFunctionListEntry {
  let mut ret_vec:Vec<q::JSCFunctionListEntry> = Vec::default();
  for item in lst {
     let citem = q::JSCFunctionListEntry {
       name:item.name,
       prop_flags:item.prop_flags,
       def_type:item.def_type,
       magic:item.magic,
       u:item.u.to_c(ctx)
     };
     ret_vec.push(citem);
  };
  let idx = ctx.proplist.len();
  ctx.proplist.push(ret_vec);
  let refput = ctx.proplist.get(idx).unwrap();
  refput.as_ptr() as *mut q::JSCFunctionListEntry
}



